package com.pig4cloud.pig.common.security.component;

import com.bblocks.enums.AppTypeEnum;
import com.bblocks.pojo.ComUser;
import com.pig4cloud.pig.common.core.util.SpringContextHolder;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.util.WebUtils;
import com.pig4cloud.pig.common.security.service.PigxUserDetailsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

import java.security.Principal;
import java.util.Comparator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author lengleng
 * @date 2022/5/28
 */
@Slf4j
@RequiredArgsConstructor
public class PigxCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

	private final OAuth2AuthorizationService authorizationService;

	@Override
	public OAuth2AuthenticatedPrincipal introspect(String token) {
		OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
		if (Objects.isNull(oldAuthorization)) {
			throw new InvalidBearerTokenException(token);
		}

		// 客户端模式默认返回
		if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) {
			return new PigxClientCredentialsOAuth2AuthenticatedPrincipal(
					Objects.requireNonNull(oldAuthorization.getAccessToken().getClaims()),
					AuthorityUtils.NO_AUTHORITIES, oldAuthorization.getPrincipalName());
		}

		Map<String, PigxUserDetailsService> userDetailsServiceMap = SpringContextHolder
				.getBeansOfType(PigxUserDetailsService.class);

		AppTypeEnum appType = (AppTypeEnum)WebUtils.getRequest().getAttribute(com.pig4cloud.pig.common.core.constant.SecurityConstants.APP_TYPE_KEY);
		Optional<PigxUserDetailsService> optional = userDetailsServiceMap.values().stream()
				.filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(),
						oldAuthorization.getAuthorizationGrantType().getValue()
					,appType
				))
				.max(Comparator.comparingInt(Ordered::getOrder));

		UserDetails userDetails = null;
		if(optional.isPresent()) {
			try {
				Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName());
				UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal;
				Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal();
				userDetails = optional.get().loadUserByUser((ComUser) tokenPrincipal);
			} catch (UsernameNotFoundException notFoundException) {
				log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage());
				throw notFoundException;
			} catch (Exception ex) {
				log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage());
			}
		}else
			throw new OAuth2AuthenticationException(String.format("未实现客户端:%s 类型:%s 数据查询业务！", Objects.requireNonNull(oldAuthorization).getRegisteredClientId(), oldAuthorization.getAuthorizationGrantType().getValue()));

		// 注入客户端信息，方便上下文中获取
		ComUser pigxUser = (ComUser) userDetails;
		Objects.requireNonNull(pigxUser).getAttributes().put(SecurityConstants.CLIENT_ID,
				oldAuthorization.getRegisteredClientId());
		return pigxUser;
	}

}
